/**
 * @description 1、防抖函数，只执行规定时间内触发的最后一次
 * @param {() => void} fn 要执行的回调函数
 * @param {number} duration 延迟执行的时间，默认500毫秒
 * @return () => void
 */
export function debounce<A extends any[], R>(
    fn: (...args: A) => R,
    duration: number = 500
): (...args: A) => void {
    let timer: ReturnType<typeof setTimeout> | null = null;

    return function (this: any, ...args: A) {
        if (timer) {
            clearTimeout(timer);
            timer = null;
        }

        timer = setTimeout(() => {
            fn.apply(this, args);
        }, duration);
    };
}

/**
 * @description 2、节流函数，只执行规定时间内的第一次（所谓节流，就是指连续触发事件，但是在n 秒中只执行一次函数，节流会稀释函数的执行频率)
 * @param {() => void} fn 要执行的回调函数
 * @param {number} duration 延迟执行的时间，默认500毫秒
 * @return () => void
 */
export function throttle<A extends any[], R>(
    fn: (...args: A) => R,
    duration: number = 500
): (...args: A) => void {
    let start = Date.now();

    return function (this: any, ...args: A) {
        const end = Date.now();

        if (end - start >= duration) {
            fn.apply(this, args);
            start = Date.now();
        }
    };
}

/**
 * @description 3、粗略的计算时间格式
 * @param {number | string} time 需要处理的时间戳，毫秒
 * @return string
 */
export const roughFormatDate = (time: number | string | Date): string => {
    if (getType(time as Date) === 'date') {
        time = Date.now() - (time as Date).getTime();
    }

    if (getType(time as string) === 'string') {
        time = +time;
    }

    const s = (time as number) / 1000;

    if (s / 3600 / 24 / 30 / 12 >= 1) return Math.floor(s / 3600 / 24 / 30 / 12) + '年';
    if (s / 3600 / 24 / 30 >= 1) return Math.floor(s / 3600 / 24 / 30) + '个月';
    if (s / 3600 / 24 >= 1) return Math.floor(s / 3600 / 24) + '天';
    if (s / 3600 >= 1) return Math.floor(s / 3600) + '小时';
    if (s / 60 >= 1) return Math.floor(s / 60) + '分钟';

    return Math.floor(s) + '秒';
};

/**
 * @description 4、比较精确的计算时间格式，按30 天一月为准，显示上一年会多5天
 * @param {number | string} time 需要处理的时间戳，毫秒
 * @return string
 */
export const exactFormatDate = (time: number | string | Date): string => {
    if (getType(time as Date) === 'date') {
        time = Date.now() - (time as Date).getTime();
    }

    if (getType(time as string) === 'string') {
        time = +time;
    }

    const s = (time as number) / 1000;
    const formatTime = (time: number) => (time >= 10 ? `${time}` : `0${time}`);

    const year = Math.floor(s / 31104000);
    const $y = year * 31104000; // 12 * 30 * 24 * 3600

    const month = Math.floor((s - $y) / 2592000);
    const $m = month * 2592000; // 30 * 24 * 3600

    const day = Math.floor((s - $y - $m) / 86400);
    const $d = day * 86400; // 24 * 3600

    const hour = Math.floor((s - $y - $m - $d) / 3600);
    const $h = hour * 3600; // 60 * 60

    const minute = Math.floor((s - $y - $m - $d - $h) / 60);
    const sec = s - $y - $m - $d - $h - minute * 60;

    if (year >= 1)
        return `${year}年${formatTime(month)}个月${formatTime(day)}天${formatTime(
            hour
        )}小时${formatTime(minute)}分${formatTime(sec)}秒`;
    if (month >= 1)
        return `${month}个月${formatTime(day)}天${formatTime(hour)}小时${formatTime(
            minute
        )}分${formatTime(sec)}秒`;
    if (day >= 1)
        return `${day}天${formatTime(hour)}小时${formatTime(minute)}分${formatTime(sec)}秒`;
    if (hour >= 1) return `${hour}小时${formatTime(minute)}分${formatTime(sec)}秒`;
    if (minute >= 1) return `${minute}分${formatTime(sec)}秒`;

    return Math.floor(s) + '秒';
};

/**
 * @description 5、深拷贝
 * @param {any} target 拷贝的对象
 * @param {WeakMap} map 存储拷贝对象的map容器
 * @return any
 */
export const deepClone = <T>(
    target: T,
    map: WeakMap<Record<string | symbol, any>, any> = new WeakMap()
): T => {
    if (typeof target !== 'object' || target === null) return target;
    if (map.has(target)) return map.get(target);

    const cloneTarget = Object.create(target);
    map.set(target, cloneTarget);

    for (const key in target) {
        const value = target[key];

        if (Object.prototype.hasOwnProperty.call(target, key)) {
            cloneTarget[key] = deepClone(value, map);
        }
    }

    return cloneTarget;
};

/**
 * @description 6、格式化时间戳
 * @param {number | string | Date} date 时间戳
 * @param {DateFormat} format 格式化的数据模板
 * @return string | number | Date
 */
type UppercaseFormat =
    | 'L'
    | 'LL'
    | 'LLL'
    | 'LLLL'
    | 'LLLLL'
    | 'LLLLLL'
    | 'LLLLLLL'
    | 'LLLLLLLL'
    | 'LLLLLLLLL'
    | 'LLLLLLLLLL'
    | 'X'
    | 'XX'
    | 'XXX'
    | 'XXXX';
type LowercaseFormat =
    | 'l'
    | 'll'
    | 'lll'
    | 'llll'
    | 'lllll'
    | 'llllll'
    | 'lllllll'
    | 'llllllll'
    | 'x'
    | 'xx'
    | 'xxx'
    | 'xxxx';
type DateFormat = UppercaseFormat | LowercaseFormat;

export const formatDate = (date: number | string | Date, format: DateFormat = 'LLLLLLLLLL') => {
    const time = new Date(Number(date));
    const year = time.getFullYear();
    const month = time.getMonth();
    const day = time.getDate();
    const hour = time.getHours();
    const minute = time.getMinutes();
    const second = time.getSeconds();

    const realMonth = month + 1;
    const months = [
        '一月',
        '二月',
        '三月',
        '四月',
        '五月',
        '六月',
        '七月',
        '八月',
        '九月',
        '十月',
        '十一月',
        '十二月',
    ];
    const formatTime = (time: number) => (time < 10 ? '0' + time : time);

    const exactMonth = formatTime(realMonth);
    const exactDay = formatTime(day);
    const exactHour = formatTime(hour);
    const exactMinute = formatTime(minute);
    const exactSecond = formatTime(second);

    const result = {
        L: `${year}年` /* YYYY年  2019年 */,
        l: year /* YYYY  2019 */,
        LL: months[month] /* MMMM  五月 */,
        ll: `${realMonth}月` /* MMM  5月 */,
        LLL: `${exactDay}日` /* DD日  06日 */,
        lll: day /* DD  16 */,
        LLLL: `${year}年${exactMonth}月` /* YYYY年MM月  2019年05月 */,
        llll: `${year}${exactMonth}` /* YYYYMM  201905 */,
        LLLLL: `${year}年${exactMonth}月${exactDay}日` /* YYYY年MM月DD日  2019年05月16日 */,
        lllll: `${year}年${realMonth}月${day}日` /*	YYYY年M月D日  2019年5月16日 */,
        LLLLLL: `${year}/${exactMonth}/${exactDay}` /* YYYY/MM/DD	2019/05/16 */,
        llllll: `${year}/${realMonth}/${day}` /* YYYY/M/D  2019/5/16 */,
        LLLLLLL: `${year}-${exactMonth}-${exactDay}` /* YYYY-MM-DD  2019-05-16 */,
        lllllll: `${year}-${realMonth}-${day}` /* YYYY-M-D  2019-5-16 */,
        LLLLLLLL: `${year}${exactMonth}${exactDay}` /* YYYYMMDD  20190516 */,
        llllllll: `${year}${realMonth}${day}` /* YYYYMD  2019516 */,
        LLLLLLLLL: `${year}/${exactMonth}/${exactDay} ${exactHour}:${exactMinute}:${exactSecond}` /* YYYY/MM/DD HH:mm  2019/05/16 09:10:48 */,
        LLLLLLLLLL: `${year}-${exactMonth}-${exactDay} ${exactHour}:${exactMinute}` /* YYYY-MM-DD HH:mm  2019-05-16 09:10 */,
        x: `${exactHour}:${exactMinute}` /* HH:mm  18:18 */,
        xx: `${exactMonth}月` /* MM月  06月 */,
        xxx: `${year}/` /* YYYY /  2020 / */,
        X: exactMonth /* MM  01 */,
        XX: `${exactMonth}.${exactDay}.${year}` /* MM.DD.YYYY  01.01.2020 */,
        XXX: `${year}.${exactMonth}.${exactDay}` /* YYYY.MM.DD  2020.01.01 */,
        XXXX: `${exactMonth}-${exactDay} ${exactHour}:${exactMinute}` /* MM-DD HH:mm  05-01 09:06 */,
        xxxx: `${realMonth}-${day} ${hour}:${minute}` /* M-D H:m  5-1 9:6 */,
    };

    return result[format] || date;
};

/**
 * @description 7、对数值进行四舍五入，默认保留两位小数，尾数为0 自动省略
 * @param {number | string} num 需要进行四舍五入的数据
 * @param {number} decimal 保留的位数
 * @return number
 */
export const exactRound = (num: number | string, decimal: number = 2): number => {
    // 1、组装数据，先乘做处理，然后做除法
    // const targetNumber = Number(num);

    // 保留几位小数，先翻倍进行四舍五入再保留位数
    // const doubleNumber = Number(1 + ''.padEnd(decimal, '0'));
    // // const doubleNumber = Number([1, ...(new Array(decimal)).fill(0)].join(''));
    // return Math.round(targetNumber * doubleNumber) / doubleNumber;

    // 2、使用科学计数法，本质还是先乘后除
    const pair = `${num}e${decimal}`;
    const value = Math.round(+pair);

    return +`${value}e${-decimal}`;
};

/**
 * @description 8、对数值进行处理，实现千位分隔符
 * @param {number | string} num 需要处理的数据
 * @return string
 */
export const formatThousandSeparator = (num: number | string): string => {
    // 1、toLocaleString()，精度丢失
    // return Number(num).toLocaleString('en-US');

    // 2、正则，不能处理小数
    // return String(num).replace(/(\d)(?=(\d{3})+$)/g, '$1,');

    // 3、反向截取添加
    // eslint-disable-next-line prefer-const
    let [integer, decimals] = (num + '').split('.');
    const length = integer.length;

    for (let i = 1; i < length / 3; i++) {
        const target = integer.slice(0, -4 * i + 1);
        integer = integer.replaceAll(target, target + ',');
    }

    return integer + (decimals ? `.${decimals}` : '');

    // 4、遍历处理，遍历太多不合适
    // const stringValue = String(num);
    // const formatChars = (chars: string) => {
    //   const result = chars
    //     .split('')
    //     .reverse()
    //     .flatMap((item, index) => (index + 1) % 3 === 0 ? [item, ','] : item)
    //     .reverse()
    //     .join('');

    //   return result[0] === ',' ? result.slice(1) : result;
    // }

    // if (stringValue.includes('.')) {
    //   const index = stringValue.indexOf('.');
    //   return formatChars(stringValue.slice(0, index)) + stringValue.slice(index);
    // }

    // return formatChars(stringValue);
};

/**
 * @description 9、对运算进行处理，解决精度问题
 * @param {number[]} arr 需要运算的数据
 * @param {OperationType} type 运算的符号
 * @return number
 */
type OperationType = '+' | '-' | '*' | '/';

export const precisionOperation = (arr: number[], type: OperationType): number => {
    const isInteger = (arr: number[]) => arr.every((num) => Number.isInteger(num));

    const map = {
        '+': (arr: number[], step: number = 1) => {
            return arr.reduce((pre, num) => pre + num) / step;
        },
        '-': (arr: number[], step: number = 1) => {
            return arr.reduce((pre, num) => pre - num) / step;
        },
        '*': (arr: number[], step: number = 1) => {
            return arr.reduce((pre, num) => pre * num) / step;
        },
        '/': (arr: number[], step: number = 1) => {
            return arr.reduce((pre, num) => pre / num) / step;
        },
    };

    const handler = map[type];
    if (!handler) throw Error('type 应该为 + - * /');

    if (isInteger(arr)) return handler(arr);

    const handleNumber = (arr: number[]) => {
        const charsNumber: string[] = arr.map((num) => {
            let chars = String(num);

            return !chars.includes('.') ? (chars += '.') : chars;
        });

        const decimalsLength = charsNumber.map((num) => num.split('.')[1].length);
        const max = Math.max(...decimalsLength);

        // 1、借助字符串操作解决
        // const maxChars = ''.padEnd(max, '0');
        // const mulriple = Number(1 + ''.padEnd(max, '0'));
        // const result = charsNumber.map(num => {
        //   const chars = num + maxChars;
        //   const index = chars.indexOf('.') + max;
        //   const stringNumber = chars.replace('.', '').slice(0, index);

        //   return Number(stringNumber);
        // });

        // 2、使用科学计数法
        const mulriple = +`1e${max}`;
        const result = charsNumber.map((num) => +`${+num}e${max}`);

        return [result, mulriple];
    };

    const [numberArray, mulriple] = handleNumber(arr) as [number[], number];

    if (type === '*') return handler(numberArray, Math.pow(mulriple, 2));
    if (type === '/') return handler(numberArray);

    return handler(numberArray, mulriple);
};

/**
 * @description 10、判断数据类型
 * @param {any} value 数据
 * @return 'string' | 'number' | 'null' | 'undefined' | 'boolean' | 'bigint' | 'symbol' | 'array' | 'function' | 'date' | 'object' | unknown
 */
export function getType(value: string): 'string';
export function getType(value: number): 'number';
export function getType(value: null): 'null';
export function getType(value: undefined): 'undefined';
export function getType(value: boolean): 'boolean';
export function getType(value: bigint): 'bigint';
export function getType(value: symbol): 'symbol';
export function getType(value: any[]): 'array';
export function getType(value: Function): 'function';
export function getType(value: Date): 'date';
export function getType(value: object): 'object';
export function getType(value: unknown): unknown {
    if (value === null) return 'null';
    if (typeof value !== 'object') return typeof value;

    return Object.prototype.toString.call(value).slice(8, -1).toLocaleLowerCase();
}

/**
 * @description 11、判断对象是否为空
 * @param {object} target 检测的对象
 * @return boolean
 */
export const isNullObject = (target: Record<string | symbol, any>): boolean | void => {
    if (target === null) {
        return console.error(`TypeError: 检查的对象不合法: ${null}`);
    }

    if (typeof target !== 'object' && target !== null) {
        return console.error(`TypeError: 检查对象不合法: ${target}`);
    }

    if (Array.isArray(target) && target.length === 0) return true;

    if (target.constructor === Object && Reflect.ownKeys(target).length === 0) return true;

    return false;
};

/**
 * @description 12、生成随机数
 * @param {number} min 最小值
 * @param {number} max 最大值
 * @return number
 */
export const randomNumber = (min: number, max: number): number => {
    const num = Math.round(Math.random() * Math.abs(max - min) + Math.min(min, max));

    return num === 0 ? 0 : num;
};

/**
 * @description 13、大小写转换
 * @param {string} target 转换的字符
 * @param {'XX' | 'xx' | 'X_xx' | 'X_XX'} type 转换的类型
 * @return string
 */
type CaseType = 'XX' | 'xx' | 'X_xx' | 'X_XX';

export const turnCase = (target: string, type: CaseType = 'X_XX'): string => {
    const chars = target.trimStart();
    if (chars === '') return target;

    const typeMap: Record<CaseType, (data: string) => string> = {
        XX: (data) => data.toUpperCase(),
        xx: (data) => data.toLowerCase(),
        X_xx: (data) => data[0].toUpperCase() + data.slice(1),
        X_XX: (data) =>
            data.split(' ').reduce((pre, v) => {
                if (v === '') {
                    pre += ' ';
                } else {
                    pre += v[0].toUpperCase() + v.slice(1) + ' ';
                }

                return pre;
            }, ''),
    };

    const handler = typeMap[type];
    if (!handler) return chars;

    return handler(chars);
};

/**
 * @description 14、指定数组去重，只考虑第一层数据
 * @param {any[]} arr 需要去重的数组
 * @param {string} key 数组中每一项去重的key 值
 * @return any[]
 */
export const removeRepetitive = <T>(arr: Array<T>, key?: keyof T) => {
    const isBasicType = (target: any) => {
        if (target === null) return true;

        return ['string', 'number', 'boolean', 'undefined', 'bigint', 'symbol'].includes(
            typeof target
        );
    };

    const isBasic = arr.every((item) => isBasicType(item));
    if (isBasic) return [...new Set(arr)];

    if (key) {
        const keys = new Set();

        return arr.reduce((pre, item) => {
            const value = item[key];
            if (keys.has(value)) return pre;

            pre.push(item);
            keys.add(value);
            return pre;
        }, [] as Array<T>);
    }

    return arr;
};

/**
 * @description 15、处理十六进制的色号
 * @param {string} color 颜色色号
 * @param {number} opacity 透明度
 * @return string
 */
export const colorConvertRgba = (color: `#${string}`, opacity: number = 1) => {
    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    if (!reg.test(color)) return color;

    let rgbColor = color.slice(1);

    // 符号十六进制色号，进行转换
    // 如果只有三位的值，需变成六位，如：#fff => #ffffff、#abc => #aabbcc
    if (rgbColor.length === 3) {
        let newColor = '';
        for (let i = 0; i < 3; i++) {
            const target = rgbColor[i];
            newColor += target.replace(target, target + target);
        }

        rgbColor = newColor;
    }

    // 处理六位的颜色值，转为RGB
    const colors: Array<number> = [];
    for (let i = 0; i < 6; i += 2) {
        colors.push(parseInt('0x' + rgbColor.slice(i, i + 2)));
    }

    return `rgba(${colors.join(',')}, ${opacity})`;
};

/**
 * @description 16、中文格式化数值（数值转中文/中文大写）
 * @param {number | string} target 需要转化的数值
 * @param {boolean | undefined} isChineseCapital 是否需要转成中文大写
 * @return string
 */
interface Map {
    /* 数字元素 */
    numeral: Record<string, string>;
    /* 小单位 */
    unit: Record<string, string>;
    /* 大单位 */
    series: Record<string, string>;
}

export const formatChineseNumeral = (target: number | string, isChineseUppercase = false) => {
    const num = target + '';
    if (num.length > 12 || num.includes('.')) {
        return console.warn('数值不合规，非整数或者超过了10000亿');
    }

    const createMap = (isUppercase = false): Map => {
        const capital: Map = {
            numeral: {
                0: '零',
                1: '一',
                2: '二',
                3: '三',
                4: '四',
                5: '五',
                6: '六',
                7: '七',
                8: '八',
                9: '九',
            },
            unit: {
                1: '',
                2: '十',
                3: '百',
                4: '千',
            },
            series: {
                1: '',
                2: '万',
                3: '亿',
            },
        };

        const chineseUppercase: Map = {
            numeral: {
                0: '零',
                1: '壹',
                2: '贰',
                3: '叁',
                4: '肆',
                5: '伍',
                6: '陆',
                7: '柒',
                8: '捌',
                9: '玖',
            },
            unit: {
                1: '',
                2: '拾',
                3: '佰',
                4: '仟',
            },
            series: {
                1: '',
                2: '万',
                3: '亿',
            },
        };

        return isUppercase ? chineseUppercase : capital;
    };

    const map = createMap(isChineseUppercase);

    /* 格式化数值，将数值拆分成四个一组 => [1, 2345, 6789] */
    const formatNumber = (targetNumber: string) => {
        for (let i = 1; i < targetNumber.length / 4; i++) {
            const target = targetNumber.slice(0, -5 * i + 1);

            targetNumber = targetNumber.replace(target, target + ',');
        }

        return targetNumber.split(',');
    };

    /* 将数值转化成中文 */
    const toChineseNumeral = (numeral: string): string => {
        let chars = '';
        let length = numeral.length;

        for (const value of numeral) {
            if (value === '0') {
                if (chars[chars.length - 1] === '零') {
                    chars = chars.slice(0, -1);
                }

                chars += map.numeral[value];
                length--;
            }
            /* 特别处理 10、010 => 十、零一十 */
            // else if (numeral.length === 2 && value === '1') {
            //   if (map.numeral[value + 1] === '零') {
            //     chars += map.numeral[value] + map.unit[length--]
            //   } else {
            //     chars += map.unit[length--]
            //   }
            // }
            else {
                chars += map.numeral[value] + map.unit[length--];
            }
        }

        return chars[chars.length - 1] === '零' ? chars.slice(0, -1) : chars;
    };

    /* 处理格式化好的数组 */
    const handleFormatNumber = (data: string[]) => {
        let result = '';
        let length = data.length;

        data.forEach((i) => {
            const chineseNumeral = toChineseNumeral(i);

            if (chineseNumeral === '') {
                result += '';
                length--;
            } else {
                result += chineseNumeral + map.series[length--];
            }
        });

        return result;
    };

    const result = handleFormatNumber(formatNumber(num));

    return isChineseUppercase ? `${result}元整` : result;
};

/**
 * @description 16、判断数组中是否存在某个值
 * @param {Array<T>} list 目标数组
 * @param {keyof T | ((item: T) => any)} key 收集数据的key
 * @return [Set<number | string>, (target: any) => boolean]
 */
export const createCheckoutKey = <T>(
    list: Array<T>,
    key: keyof T | ((item: T) => any)
): [Set<number | string>, (target: any) => boolean] => {
    const set: Set<number | string> = new Set();
    const handler = typeof key === 'function' ? key : (item: T) => item[key];

    for (let i = 0; i < list.length; i++) {
        const id = handler(list[i]);
        set.add(id);
    }

    return [set, (target: any) => set.has(target)];
};
